use std::{
    collections::HashMap, fmt::Display, fs::File, io::{self, BufRead, BufReader}
};

struct Ymd(u16, u8, u8);

impl Display for Ymd{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!("{}年{:02}月{:02}日", self.0, self.1, self.2))
    }
}

struct AdminCode {
    index: HashMap<String, String>,
}

impl AdminCode {
    fn new() -> Self {
        let mut ret = AdminCode {
            index: HashMap::new(),
        };
        let _ = ret.init();
        ret
    }
    fn init(&mut self) -> io::Result<()> {
        let file = File::open("admincode.txt")?;

        let mut reader = BufReader::new(file);
        let mut line = String::new();

        while let Ok(s) = reader.read_line(&mut line) {
            if s == 0 {
                break;
            }

            if let Some((k, v)) = line.trim().split_once(" ") {
                self.index.insert(k.to_string(), v.to_string());
            }
            line.clear();
        }
        Ok(())
    }
    fn to_area(&self, admin_code: &str) -> String {
        self.index
            .get(admin_code)
            .unwrap_or(&"".to_string())
            .to_string()
    }
}

fn to_18(id_card_no: &str) -> Result<String, &'static str> {
    if id_card_no.len() != 15 {
        return Err("len");
    }
    for c in id_card_no.chars() {
        if !c.is_ascii_digit() {
            return Err("format");
        }
    }
    Ok(format!("{}19{}0", &id_card_no[0..6], &id_card_no[6..]))
}

fn check18(id_card_no: &str) -> Result<(), &'static str> {
    const WEIGHTS: [u32; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    if id_card_no.len() != 18 {
        return Err("len");
    }
    let mut sum = 0;
    for (i, c) in id_card_no.char_indices() {
        if i == 17 {
            return if 10 - (sum + 9) % 11
                == if c == 'x' || c == 'X' {
                    10
                } else {
                    c.to_digit(10).ok_or("format")?
                } {
                Ok(())
            } else {
                Err("sign")
            };
        }
        let n = c.to_digit(10).ok_or("format")?;
        sum += n * WEIGHTS[i]
    }
    Err("len")
}

fn parse(id_card_no: &str) -> Result<(String, Ymd, bool), &'static str> {
    let card_no = match id_card_no.len() {
        15 => &to_18(id_card_no)?,
        18 => {
            check18(id_card_no)?;
            id_card_no
        }
        _ => return Err("len"),
    };
    Ok((
        card_no[0..6].to_string(),
        Ymd(
            card_no[6..10].parse().or(Err("format"))?,
            card_no[10..12].parse().or(Err("format"))?,
            card_no[12..14].parse().or(Err("format"))?,
        ),
        card_no.chars().nth(16).ok_or("foramt")? as i32 % 2 == 0,
    ))
}

pub fn check_id_card(id_card_no: &str) -> String {
    let admincode = AdminCode::new();

    match parse(id_card_no) {
        Ok((admin_code, ymd, flag)) => {
            format!(
                "身份证号码正确,{},{},{}",
                if flag { "女" } else { "男" },
                ymd,
                admincode.to_area(&admin_code)
            )
        }
        Err(_) => "身份证号码错误".to_string(),
    }
}
